﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using LogicalComponent.rules.grid;
using LogicalComponent.util;
using LogicalComponent.util.exception;

namespace TestLogicalComponent.rules.grid
{
    [TestClass]
    [DeploymentItem(@"MapSample.txt")]
    public class TestSearchAlgorithm
    {
        const string MAP = @"MapSample.txt";

        [TestMethod]
        public void TestMapReader()
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);

            Coordinate[] waters = {new Coordinate(0, 0), new Coordinate(reader.Width - 1, reader.Length - 1),
                                   new Coordinate(reader.Width - 1, 0), new Coordinate(0, reader.Length -1),
                                   new Coordinate(11, 9), new Coordinate(27, 23)};

            foreach (Coordinate water in waters)
            {
                TileLogic tile = grid.GetTile(water);
                Assert.AreEqual(tile.MoveModType, MoveCostModType.IMPASSABLE);
            }

            Coordinate[] plains = {new Coordinate(9, 1), new Coordinate(31, 23)};

            foreach (Coordinate plain in plains)
            {
                TileLogic tile = grid.GetTile(plain);
                Assert.AreEqual(tile.MoveModType, MoveCostModType.ADDER);
                Assert.AreEqual(tile.MoveModValue, 0);
            }
        }

        [TestMethod]
        public void TestInvalidRuleSet()
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);
            SquareMoveRuleSet foo = new SquareMoveRuleSet((IGrid)grid);

            Tuple<Fraction, Fraction>[] badMins = {new Tuple<Fraction, Fraction>(new Fraction(1), new Fraction(3)),
                                                   new Tuple<Fraction, Fraction>(new Fraction(1), new Fraction(1, 2)),
                                                  };

            foreach (Tuple<Fraction, Fraction> badMin in badMins)
            {
                try
                {
                    SquareMoveRuleSet rules = new SquareMoveRuleSet(grid);
                    rules.MinOrthagonalCost = badMin.Item1;
                    rules.MinDiagonalCost = badMin.Item2;

                    Assert.Fail();
                }
                catch (LogicalUserError){ }
                try
                {
                    SquareMoveRuleSet rules = new SquareMoveRuleSet(grid);
                    rules.MinDiagonalCost = badMin.Item2;
                    rules.MinOrthagonalCost = badMin.Item1;

                    Assert.Fail();
                }
                catch (LogicalUserError){ }
            }
        }

        [TestMethod]
        public void TestStayInOneSpotPath()
        {
            TestPathCosts(new Coordinate(8, 9), new Coordinate(8, 9), new Fraction(0));
        }

        [TestMethod]
        public void TestSimpleOrthagonalPath()
        {
            TestPathCosts(new Coordinate(8, 9), new Coordinate(8, 12), new Fraction(3));
        }

        [TestMethod]
        public void TestSimpleDiagonalPath()
        {
            TestPathCosts(new Coordinate(9, 9), new Coordinate(6, 12), new Fraction(9, 2));
        }

        [TestMethod]
        public void TestSimpleOrhtagonalAndDiagonalPath()
        {
            TestPathCosts(new Coordinate(9, 9), new Coordinate(8, 12), new Fraction(7, 2));
        }

        [TestMethod]
        public void TestImpassableToImpassable()
        {
            TestNullPath(new Coordinate(0, 0), new Coordinate(0, 1));
        }

        [TestMethod]
        public void TestGroundToImpassable()
        {
            TestNullPath(new Coordinate(29, 15), new Coordinate(1, 1));
        }

        [TestMethod]
        public void TestImpassableToGroundImpassableInBetween()
        {
            TestNullPath(new Coordinate(0, 0), new Coordinate(29, 15));
        }

        [TestMethod]
        public void TestImpassableToGroundNeighborWorks()
        {
            TestPathCosts(new Coordinate(8, 1), new Coordinate(9, 2), new Fraction(3, 2));
        }

        [TestMethod]
        public void TestCrossImpassable()
        {
            TestNullPath(new Coordinate(7, 21), new Coordinate(9, 18));
        }

        [TestMethod]
        public void TestThroughMultipliersWithClears()
        {
            TestPathCosts(new Coordinate(40, 3), new Coordinate(43, 9), new Fraction(39, 2));
        }

        [TestMethod]
        public void TestDiagonalThroughAdders()
        {
            TestPathCosts(new Coordinate(9, 4), new Coordinate(12, 7), new Fraction(15, 2));
        }

        [TestMethod]
        public void TestComplexPath()
        {
            TestPathCosts(new Coordinate(19, 17), new Coordinate(28, 18), new Fraction(31, 2));
        }

        [TestMethod]
        public void TestComplexPath2()
        {
            TestPathCosts(new Coordinate(23, 17), new Coordinate(23, 13), new Fraction(6));
        }

        [TestMethod]
        public void TestNullFlood()
        {
            Coordinate source = new Coordinate(20, 10);
            Tuple<Coordinate, Fraction>[] expected = new Tuple<Coordinate,Fraction>[1]; 
            expected[0] = new Tuple<Coordinate,Fraction>(new Coordinate(20, 10), new Fraction(0));
            Coordinate[] notExpected = {source.North, source.NorthWest, source.West, source.SouthWest,
                                        source.South, source.SouthEast, source.East, source.NorthEast};
            TestFlood(true, source, new Fraction(0), expected, notExpected); 
        }

        [TestMethod]
        public void TestSimpleFlood()
        {
            Coordinate source = new Coordinate(20, 10);
            Tuple<Coordinate, Fraction>[] expected = {
                             new Tuple<Coordinate, Fraction>(source, new Fraction(0)),
                             new Tuple<Coordinate, Fraction>(source.North.North.North, new Fraction(3)),
                             new Tuple<Coordinate, Fraction>(source.NorthWest.NorthWest, new Fraction(3)),
                             new Tuple<Coordinate, Fraction>(source.East.NorthEast, new Fraction(5, 2)),
                                                     }; 
            Coordinate[] notExpected = {source.North.North.North.North, source.North.North.NorthEast};
            TestFlood(true, source, new Fraction(3), expected, notExpected); 
        }

        [TestMethod]
        public void TestImpassableFlood()
        {
            Coordinate source = new Coordinate(10, 19);
            Tuple<Coordinate, Fraction>[] expected = {
                             new Tuple<Coordinate, Fraction>(source, new Fraction(0)),
                                                     }; 
            Coordinate[] notExpected = {source.West, source.SouthWest, source.South, source.SouthEast, 
                                        source.East, source.NorthEast};
            TestFlood(true, source, new Fraction(3), expected, notExpected); 
        }

        [TestMethod]
        public void TestComplexFlood()
        {
            Coordinate source = new Coordinate(23, 17);
            Tuple<Coordinate, Fraction>[] expected = {
                             new Tuple<Coordinate, Fraction>(source, new Fraction(0)),
                             new Tuple<Coordinate, Fraction>(source.South.SouthEast, new Fraction(11, 2)),
                             new Tuple<Coordinate, Fraction>(source.North.North.North.North, new Fraction(6)),
                             new Tuple<Coordinate, Fraction>(source.North.NorthEast, new Fraction(11, 2)),
                             new Tuple<Coordinate, Fraction>(source.North.North.NorthEast, new Fraction(11, 2)),
                                                     }; 
            Coordinate[] notExpected = {source.North.North.North.North.North, source.North.NorthWest};
            TestFlood(true, source, new Fraction(6), expected, notExpected); 
        }

        [TestMethod]
        public void TestComplexFloodNotFullCost()
        {
            Coordinate source = new Coordinate(23, 17);
            Tuple<Coordinate, Fraction>[] expected = {
                             new Tuple<Coordinate, Fraction>(source, new Fraction(0)),
                             new Tuple<Coordinate, Fraction>(source.South.SouthEast, new Fraction(11, 2)),
                             new Tuple<Coordinate, Fraction>(source.North.North.North.North, new Fraction(6)),
                             new Tuple<Coordinate, Fraction>(source.North.NorthEast, new Fraction(11, 2)),
                             new Tuple<Coordinate, Fraction>(source.North.North.NorthEast, new Fraction(11, 2)),
                             new Tuple<Coordinate, Fraction>(source.North.NorthWest, new Fraction(7)),
                                                     }; 
            Coordinate[] notExpected = {source.North.North.North.North.North};
            TestFlood(false, source, new Fraction(6), expected, notExpected); 
        }

        [TestMethod]
        public void TestSimpleHex()
        {
            Coordinate source = new Coordinate(28, 11);
            Coordinate destination = new Coordinate(24, 11);
            TestHexMoveRules(source, destination, new Fraction(4));
        }

        [TestMethod]
        public void TestSpecialHexAdjacency()
        {
            Coordinate source = new Coordinate(28, 11);
            Coordinate destination = new Coordinate(26, 13);
            TestHexMoveRules(source, destination, new Fraction(2));
            TestHexMoveRules(destination, source, new Fraction(2));
        }

        private void TestHexMoveRules(Coordinate source, Coordinate destination, Fraction expected)
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);
            HexMoveRuleSet ruleSet = new HexMoveRuleSet(grid);
            ruleSet.BaseCost = new Fraction(1);

            SearchAlgorithm search = new AStarSearch(ruleSet, source, destination);
            search.DoSearch();
            LogicalComponent.rules.grid.Path path = search.GetPathTo(destination);

            Fraction actual = path.Cost;
            Assert.IsTrue(expected == actual,
                          string.Format("Expected cost is {0}, but actual cost was {1}", expected, actual));

            Assert.AreEqual<Coordinate>(path.GetCoordinate(0), source);
            for (int i = 1; i < path.Length - 1; i++)
            {
                Assert.AreNotEqual<Coordinate>(path.GetCoordinate(i), source);
                Assert.AreNotEqual<Coordinate>(path.GetCoordinate(i), destination);
            }
            Assert.AreEqual<Coordinate>(path.GetCoordinate(path.Length - 1), destination);
        }

        private void TestPathCosts(Coordinate source, Coordinate destination, Fraction expected)
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);
            SquareMoveRuleSet ruleSet = new SquareMoveRuleSet((IGrid)grid);
            ruleSet.OrthagonalCost = new Fraction(1);
            ruleSet.DiagonalCost = new Fraction(3, 2);

            SearchAlgorithm search = new AStarSearch(ruleSet, source, destination);
            search.DoSearch();
            LogicalComponent.rules.grid.Path path = search.GetPathTo(destination);

            Fraction actual = path.Cost;
            Assert.IsTrue(expected == actual,
                          string.Format("Expected cost is {0}, but actual cost was {1}", expected, actual));

            Assert.AreEqual<Coordinate>(path.GetCoordinate(0), source);
            for (int i = 1; i < path.Length - 1; i++)
            {
                Assert.AreNotEqual<Coordinate>(path.GetCoordinate(i), source);
                Assert.AreNotEqual<Coordinate>(path.GetCoordinate(i), destination);
            }
            Assert.AreEqual<Coordinate>(path.GetCoordinate(path.Length - 1), destination);
        }

        private void TestNullPath(Coordinate source, Coordinate destination)
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);
            SquareMoveRuleSet ruleSet = new SquareMoveRuleSet((IGrid)grid);
            ruleSet.OrthagonalCost = new Fraction(1);
            ruleSet.DiagonalCost = new Fraction(3, 2);

            SearchAlgorithm search = new AStarSearch(ruleSet, source, destination);
            search.DoSearch();
            LogicalComponent.rules.grid.Path path = search.GetPathTo(destination);
            Assert.IsNull(path);
        }

        private void TestFlood(bool needsFullCost, Coordinate source, Fraction range, 
                               Tuple<Coordinate, Fraction>[] expected, Coordinate[] notExpected)
        {
            MapReader reader = new MapReader(MAP);
            GridLogic<TileLogic> grid = new GridLogic<TileLogic>(reader.Width, reader.Length, reader);
            SquareMoveRuleSet ruleSet = new SquareMoveRuleSet((IGrid)grid);
            ruleSet.OrthagonalCost = new Fraction(1);
            ruleSet.DiagonalCost = new Fraction(3, 2);
            ruleSet.NeedsFullCost = needsFullCost;

            SearchAlgorithm flood = new DijkstraFlood(ruleSet, source, range);
            flood.DoSearch();
            foreach (Tuple<Coordinate, Fraction> element in expected)
            {
                LogicalComponent.rules.grid.Path path = flood.GetPathTo(element.Item1);
                Assert.IsNotNull(path, String.Format("Flood did not reach '{0}' as expected", element.Item1));
                Assert.AreEqual<Fraction>(element.Item2, path.Cost);
            }

            foreach (Coordinate element in notExpected)
            {
                LogicalComponent.rules.grid.Path path = flood.GetPathTo(element);
                Fraction cost = path == null ? new Fraction(0) : path.Cost;
                Assert.IsNull(path, String.Format("The flood was unexpectadly able to plot a path " +
                                                  "from source '{0}' to '{1}', with a cost of '{2}'",
                                                  source, element, cost));
            }
        }

    }
}
